/*
 *  linux/kernel/sched.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * 'sched.c' is the main kernel file. It contains scheduling primitives
 * (sleep_on, wakeup, schedule etc) as well as a number of simple system
 * call functions (type getpid(), which just extracts a field from
 * current-task
 */
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/sys.h>
#include <linux/fdreg.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>

#include <signal.h>

/*
软盘驱动器在不工作时电动机通常不转，在实际读写软盘之前，需要等待电动机启动并达到正常的运行速度，通常需要0.5秒左右。
当对软盘的读写操作完成时，也需要让电动机停止转动。但可能马上又需要对其进行读写操作，因此在没有操作后还需让其空转一段时间，大约3秒左右，这段时间没有操作就让电动机停止转动。
当软盘的读写操作发生错误，或某些其它情况导致电动机没有被关闭，我们也需要让系统在一定时间后自动将其关闭。Linux 中这个时间为100秒。
原文链接：https://blog.csdn.net/mondaiji/article/details/126341009
*/

// 该宏取信号 nr 在信号位图中对应位的二进制数值。信号编号 1-32。
// 想要在第n位上标记成1，我们可以使用或运算  原值 |（1<<n） 的公式
#define _S(nr) (1<<((nr)-1))
// 可阻塞的信号位图：除了 SIGKILL 和 SIGSTOP 信号以外其他信号都是可阻塞的
#define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP)))

// 显示task数组中 task[nr] 元素的 task_union 中未使用的内核栈字节数
void show_task(int nr,struct task_struct * p)
{
	int i,j = 4096-sizeof(struct task_struct);

	printk("%d: pid=%d, state=%d, ",nr,p->pid,p->state);
	i=0;
	// 注意：栈是从高地址向低地址方式使用，下面的循环是从低地址向高地址开始检查
	while (i<j && !((char *)(p+1))[i])
		i++;
	printk("%d (of %d) chars free in kernel stack\n\r",i,j);
}

void show_stat(void)
{
	int i;

	for (i=0;i<NR_TASKS;i++)
		if (task[i])
			show_task(i,task[i]);
}

// 表示一个时间片对应的晶振频率
#define LATCH (1193180/HZ) // 8254 芯片的时钟输入频率为1193180：1000ms/100=10ms  <--> 1193180Hz/100

extern void mem_use(void);

extern int timer_interrupt(void);
extern int system_call(void);

// task_struct 和 4k的数组共用一个内存页，task_struct占用了内存页的低地址部分；stack从页的高地址开始向低地址压栈使用
union task_union {
	struct task_struct task;
	char stack[PAGE_SIZE];
};

static union task_union init_task = {INIT_TASK,};

// 系统从开机开始算起的滴答数（当前系统时间：以滴答为单位计数表示）
long volatile jiffies=0;
// 系统开机时刻的时间戳
long startup_time=0;
// current 代表当前正在执行的进程的task_struct指针。执行switch_to()时，会将current指向新的当前任务
struct task_struct *current = &(init_task.task);
struct task_struct *last_task_used_math = NULL;

// 任务数组
struct task_struct * task[NR_TASKS] = {&(init_task.task), };

// 声明一个数组，共 4*1k 大小的连续内存区域（页大小），用作task0的用户态堆栈
// 在内核初始化操作过程中被用作内核栈，初始化完成以后将被用作任务0的用户态堆栈。
// 在运行任务0之前它是内核栈，以后用作任务0和1的用户态栈。
long user_stack [ PAGE_SIZE>>2 ] ;

// 设置堆栈 ss：esp（数据段选择符，指针）：初始化第一个内核栈帧（高地址）的偏移值、段选择子
struct {
	long * a;  // offset，指向数组最后一个元素的地址（堆栈中的第一个栈帧）
	short b;  // 段选择子，0x10 为二进制的10000b，表示段描述符索引为10b，即为十进制的2(内核数据段)
	} stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };
/*
 *  'math_state_restore()' saves the current math information in the
 * old math state array, and gets the new ones from the current task
 */
void math_state_restore()
{
	if (last_task_used_math == current)
		return;
	__asm__("fwait");
	if (last_task_used_math) {
		__asm__("fnsave %0"::"m" (last_task_used_math->tss.i387));
	}
	last_task_used_math=current;
	if (current->used_math) {
		__asm__("frstor %0"::"m" (current->tss.i387));
	} else {
		__asm__("fninit"::);
		current->used_math=1;
	}
}

/*
 *  'schedule()' is the scheduler function. This is GOOD CODE! There
 * probably won't be any reason to change this, as it should work well
 * in all circumstances (ie gives IO-bound processes good response etc).
 * The one thing you might take a look at is the signal-handler code here.
 *
 *   NOTE!!  Task 0 is the 'idle' task, which gets called when no other
 * tasks can run. It can not be killed, and it cannot sleep. The 'state'
 * information in task[0] is never used.
 */
// 功能：进行调度，去执行其他任务
// 根据进程的时间片和优先权调度机制选择任务
void schedule(void)
{
	int i,next,c;
	struct task_struct ** p;

/* check alarm, wake up any interruptible tasks that have got a signal */
	// 逆序遍历task，将符合条件的task设置TASK_RUNNING状态、SIGALRM信号
	for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
		if (*p) {
			// 如果任务的 alarm 时间己经过期(alarm<jiffies)
			// jiffies 是系统从开机开始算起的滴答数（10ms/滴答）
			if ((*p)->alarm && (*p)->alarm < jiffies) {
					// 位或赋值：在信号位图中设置 SIGALRM 信号位为1（置位）
					(*p)->signal |= (1<<(SIGALRM-1));
					(*p)->alarm = 0;
				}
			// 如果进程的信号位图中除去被阻塞的信号外还有其他信号，并且任务处于可中断睡眠状态（TASKINTERRUPTIBLE)，
			// 则置任务为就绪状态 (TASK RUNNING)
				// “~”运算符指的是位取反运算符
			if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
			(*p)->state==TASK_INTERRUPTIBLE)
				(*p)->state=TASK_RUNNING;
		}

/* this is the scheduler proper: */

	while (1) {
		c = -1; // 最大时间片
		next = 0;
		i = NR_TASKS;
		p = &task[NR_TASKS];
		// 找出就绪状态task中couter最大task，作为next
		while (--i) {
			// 如果 p 原本指向数组 arr 的第 i 个元素，*--p 将获取第 i-1 个元素的值，并且 p 的值也会更新为指向第 i-1 个元素。
			if (!*--p)
				continue;
			if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
				c = (*p)->counter, next = i;  // c可能取值为0或大于0
		}
		// 运行至此处，c的取值：
			// 	-1：表示没有可运行状态的任务，此时直接调度至task[0]，因为此时没有其他任务可以被调度执行
			// 	0：表示有可运行的任务，但是所有可运行的任务的剩余时间片都为0
			// 大于0：表示有可运行的任务且有可运行的时间片，此时直接调度到具有最大时间片的可运行任务
		// 如果有可运行的任务，但是所有可运行的任务的剩余时间片都为0 时
		// 则根据每个任务的优先权值，更新每一个任务的 counter 值：计算方式为 counter = counter / 2 + priority
		// 并重新选择next
		if (c) break;
		for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
			if (*p)
				(*p)->counter = ((*p)->counter >> 1) +
						(*p)->priority;
	}
	switch_to(next);
}

// pause(）系统调用。转换当前任务的状态为可中断的等待状态，并重新调度。
int sys_pause(void)
{
	current->state = TASK_INTERRUPTIBLE;
	schedule();
	return 0;
}

// https://www.cnblogs.com/ming-yan/archive/2011/05/04/3126400.html
// https://blog.csdn.net/iteye_17312/article/details/82061369

// 将指定任务睡眠，放在等待队列中（一个进程（或任务）所请求的资源正忙或不在内存中时暂时切换出去）
// 使用场景：几个进程等待同一资源时。多次调用该函数，内核程序就隐式地构筑出一个等待队列。见图8-7
//  参数p指向 task_struct * 类型的内存对象，p表示一个等待队列头的指针
//  为了能修改调用该函数程序中原来就是指针变量的值，就需要传递指针’*p’的指针，即’**p’。

// 功能：当前进程进入不可中断睡眠态，挂起在等待队列上(将current加入到等待队列头部)
void sleep_on(struct task_struct **p) // p 是一个全局变量，可能被多个进程修改
{
	struct task_struct *tmp;  // tmp用来指向等待队列上的下一个进程

	if (!p)
		return;
	// task0不能sleep睡眠
	if (current == &(init_task.task))
		panic("task[0] trying to sleep");
	// 下面两句把当前进程放到等待队列头，等待队列是以堆栈方式管理的。后到的进程等在前面
	tmp = *p; // 等待队列首元素（原等待队列的头）
	*p = current; // 将等待队列头的指针设置为当前进程（将当前进程放到等待队列的头部）
	current->state = TASK_UNINTERRUPTIBLE;  // 进程进入不可中断睡眠状态
	schedule();  // 去执行其他进程
	// 当前进程被wake_up()唤醒后重新调度执行时，从此处开始执行。
	// 既然等待的资源可以用了，就应该唤醒等待队列上的所有进程，让它们再次争夺资源的使用权。
	// 这里让队列里的下一个进程也进入运行态。这样当这个进程运行
	// 时，它又会唤醒下下个进程。最终唤醒所有进程。
	if (tmp)
		tmp->state=0;
}  // 退出 sheep_on()函数，堆栈中的局部变量 tmp消失，其指向的队列变成游离队列（这些游离队列头上的进程都是运行态，这保证 schedule()函数最终还是会找到它。）

// interruptible_sleep_on与sleep_on区别在于前者可能由信号唤醒，而后者只是wakeup函数可以唤醒。
// 信号可能唤醒位于等待队列中间的某个进程

// 功能：当前进程进入可中断睡眠态，挂起在等待队列上
void interruptible_sleep_on(struct task_struct **p)
{
	struct task_struct *tmp;

	if (!p)
		return;
	if (current == &(init_task.task))
		panic("task[0] trying to sleep");
	tmp=*p;
	*p=current;
repeat:	current->state = TASK_INTERRUPTIBLE; // 进程进入可中断睡眠状态
	schedule();
	// 只有当这个等待任务被唤醒时，程序才又会返回到这里，表示进程已被明确地唤醒并执行。
	
	// 当指针 *p 所指向的不是当前任务时，表示在当前任务被放入队列后，又有新的任务被插入等待队列前部。
	// 因此我们先唤醒它们（等待队列头），而让自己仍然等待。等待这些后续进入队列的任务被唤醒执行时来唤醒本任务。
	// 于是去执行重新调度。即使中间某个进程先醒了，他也必须把*p唤醒，自己继续睡眠
	if (*p && *p != current) {
		// 如果等待队列中还有等待任务，并且队列头指针所指向的任务不是当前任务时，
		// 则将该等待任务置为可运行的就绪状态，并重新执行调度程序。
		(**p).state=0; 
		goto repeat;
	}
	// 当等待队列头为current时
	*p=NULL;  // 和wake_up()一样，产生了游离队列，需要把游离队列的头进程(tmp)唤醒
	if (tmp)
		tmp->state=0;
}

// 功能：唤醒等待队列上的头一个进程
void wake_up(struct task_struct **p)
{
	if (p && *p) {
		(**p).state=0; // 把队列上的第一个进程设为就绪态
		*p=NULL; // 把队列头指针清空，这样失去了被其他等待进程的跟踪。(变成游离等待队列)
				 // 一般情况下，游离等待队列的头进程是就绪态，所以这些进程迟早会得到运行。
	}
}

/*
 * OK, here are some floppy things that shouldn't be in the kernel
 * proper. They are here because the floppy needs a timer, and this
 * was the easiest way of doing it.
 */
// 存放等待软驱马达启动到正常转速的进程指针(等待队列数组，表示4个不同的马达等待队列)
static struct task_struct * wait_motor[4] = {NULL,NULL,NULL,NULL};
// 下面数组分别存放各软驱马达启动所需要的滴答数。程序中默认启动时间为 50个滴答（0.5 秒）
static int  mon_timer[4]={0,0,0,0};
// 下面数组分别存放各软驱在马达停转之前需维持的时间。程序中设定为10000个滴答（100 秒）。即在马达开启 100 秒之后便会关闭。
static int moff_timer[4]={0,0,0,0};
// 参考 表9-16 数字输出寄存器定义
// 对应软驱控制器中当前的 数字输出寄存器（DOR）。
// 该寄存器每位的定义如下：
// 位 7-4：分别控制 驱动器 D-A 马达的启动。1- 启动：0-关闭。可以同时控制4个马达
// 位3：1-允许 DMA 和中断请求；0-禁止 DMA 和中断请求。
// 位2：1- 允许软盘控制器工作；0一复位软盘控制器。
// 位1-0：00-11，用于选择软盘驱动器 A-D。即表示请求项要处理哪个软驱
unsigned char current_DOR = 0x0C; // 0x0c = 0b1100

// 设置DOR寄存器的值实现马达启动等功能、并设置指定软驱启动到正常运转状态所需等待时间，然后返回该等待时间。返回值为滴答数。
int ticks_to_floppy_on(unsigned int nr)
{
	extern unsigned char selected;  // 一个标识，表示软盘控制器是否已经选中了某个软驱
	unsigned char mask = 0x10 << nr;  // (0x10 = 0b 0001 0000) 设置nr号软驱马达启动位

	// current_DOR： 当前的DOR寄存器值(软驱当前情况)
	// mask : 要求的DOR寄存器值（软驱的期望目标）

	// 最多支持4个软驱
	if (nr>3)
		panic("floppy_on: nr>3");
	// 设置马达关闭时间为100s
	moff_timer[nr]=10000;		/* 100 s = very big :-) */
	// 关中断
	cli();				/* use floppy_off to turn it off */
	// 根据当前的马达启动情况，将之前已经启动的马达标识位复制到mask
	mask |= current_DOR;
	// 如果软驱选定标志为未选定任何软驱，则重新设置mask中低2位，即设置要控制的软驱编号
	if (!selected) {
		mask &= 0xFC; // 将位0-1置零，即软区号复位
		mask |= nr;  // 置零后，再将位0-1设置为nr，即软驱A-D
	}

	// 如果数字输出寄存器的当前值与要求的值不同，说明有新开启的软驱需要处理，此时将mask值传送给软盘控制器，并更新current_DOR
	if (mask != current_DOR) {
		// 向软盘控制器的端口发送DOR，实现控制驱动器马达开启、驱动器选择、启动/复位 FDC 以及允许/禁止 DMA 及中断请求。
		outb(mask,FD_DOR);
		// 如果二者的高4位不相等，即要求启动的马达还没有启动，则置相应软驱的马达启动定时器值（HZ/2= 0.5 秒或 50个滴答）
			// 异或操作，得出不同的位设置为1，然后与操作。忽略低四位
		if ((mask ^ current_DOR) & 0xf0) // 0xf0 = 0b1111 0000
			mon_timer[nr] = HZ/2;
		else if (mon_timer[nr] < 2)
			mon_timer[nr] = 2;
		// 更新当前数字输出寄存器 current_DOR
		current_DOR = mask;
	}
	sti();
	return mon_timer[nr];
}

// 开启指定的软驱（马达）
// 设置指定软驱的马达启动到正常转速所需的延时，然后睡眼等待，直到启动定时器为0后被唤醒。
// 说明：在定时中断（do_floppy_timer）过程中会一直递减判断这里设定的延时值。当延时到期，就会唤醒（wake_up）这里的等待进程。
void floppy_on(unsigned int nr)
{
	cli();
	// 设置软驱马达启动定时器，并进行睡眠。
	while (ticks_to_floppy_on(nr))
		sleep_on(nr+wait_motor);  // 睡眠，while循环体就不再执行，直到被唤醒。
	sti();
}

// 设置关闭相应软驱马达停转定时器（3秒）。
// 若不使用该函数明确关闭指定的软驱马达，则在马达开启 100 秒之后也会被关闭。
void floppy_off(unsigned int nr)
{
	// 当对软盘的读写操作完成时，也需要让电动机停止转动。但可能马上又需要对其进行读写操作，
	// 因此在没有操作后还需让其空转一段时间，大约3秒左右，这段时间没有操作就让电动机停止转动。
	moff_timer[nr]=3*HZ;
}

// 软盘定时处理程序：处理马达的启动唤醒进程和马达关闭操作(在时钟中断do_timer()中被定时调用，即每10ms调用一次)。
// 功能：随着时间推移，递减马达启动定时值、马达关闭计时值，定时到达时做相应的操作（唤醒等待进程、关闭马达）
// 具体实现：遍历4个软驱的马达：
// 如果mon_timer[i]不为0，表示处于启动阶段，则将启动剩余时间递减，直到剩余时间为0时，唤醒等待该马达启动的进程；
// 如果mon_timer[i]为0，表示处于启动完成阶段，则将关闭剩余时间递减，直到剩余时间为0时，关闭马达；
void do_floppy_timer(void)
{
	int i;
	unsigned char mask = 0x10;  // 0b 0001 0000

	// 遍历4个软驱
	for (i=0 ; i<4 ; i++,mask <<= 1) {
		// 如果与操作后为0，说明当前启动的马达并不是 mask 指定的马达，跳过。
		if (!(mask & current_DOR))
			continue;
		// 如果当前正在启动的软驱马达是mask中指定需要启动的，则判断启动剩余时间是否还有剩余,即马达启动定时到则唤醒进程。
		if (mon_timer[i]) {
			if (!--mon_timer[i])
				// 如果启动时间滴答数变为0，则唤醒等待该软驱资源的进程
				wake_up(i+wait_motor);
		// 如果剩余启动时间为0（表示启动完成了），且关闭时间也为0（马达需要关闭了）
		} else if (!moff_timer[i]) {
			// 复位相应马达启动位（清除mask相关位），并且更新数字输出寄存器。
			current_DOR &= ~mask;
			outb(current_DOR,FD_DOR);
		} else
		// 如果启动完成了，此时就需要递减关闭剩余时间。此时如果还有关闭剩余时间，则时间片-1
			moff_timer[i]--;
	}
}

#define TIME_REQUESTS 64

// 定时器数组(timer_list[64])和定时器链表结构(next_timer)。
// 该定时器链表专用于供软驱关闭马达和启动马达定时操作。这种类型定时器类似现代 Linux 系统中的动态定时器（Dynamic Timer)，仅供内核使用。
// jiffies是定时器的到期时间，fn是定时器到期后将要执行的函数指针。
static struct timer_list {
	long jiffies;               	// 定时滴答数。
	void (*fn)();					// 定时处理程序。
	struct timer_list * next;
} timer_list[TIME_REQUESTS], * next_timer = NULL; // next_timer实际上是链表的一个头指针

// 添加定时器。定时器到期时，执行指定的处理程序。
// 参数jiffies为指定的定时值(滴答数）；参数fn为相应的处理程序指针。
// https://blog.csdn.net/mcgrady_tracy/article/details/41868371
void add_timer(long jiffies, void (*fn)(void))
{
	struct timer_list * p;  // 定时器链表指针

	if (!fn)
		return;
	cli();
	// 如果小于等于0，则直接执行fn指针函数，那么它是不会被加入到定时器链表中的。
	if (jiffies <= 0)
		(fn)();
	else {
		// 找出索引最小的fn为空的元素，即从定时器数组中，找一个空闲项，此空闲项为p。
		for (p = timer_list ; p < timer_list + TIME_REQUESTS ; p++)
			if (!p->fn)
				break;
		// 如果没有空的位置，即内核中同时存在64个内核定时器了，则系统会崩溃掉。。。
		// 不用担心，现在是不会出现这个结果的，也只有软盘那个地方才用到了内核定时器。
		if (p >= timer_list + TIME_REQUESTS)
			panic("No more time requests free");
		// 如果找到空闲项，设置成员变量
		p->fn = fn;
		p->jiffies = jiffies;
		p->next = next_timer; // next_timer实际上是链表的一个头指针，而往链表新增一个节点实际上是在链表头部添加的。
		next_timer = p;
		// 此时，将当前节点加入到链表头部，然后对链表进行排序（加入新节点之前的链表已经是一个按定时值从小到大排序的链表）。排序方法类似于冒泡法。
		// (☆☆☆☆☆)链表项按定时值从小到大排序。在排序时减去排在前面需要的滴答数，
		// 这样在处理定时器时只要查看链表头的第一项的定时是否到期即可。
		while (p->next && p->next->jiffies < p->jiffies) {
			// 父节点如果大于子节点，此时设置父节点的jiffies为二者的差，然后交换二者的链表位置，使父节点jiffies小于子节点
			p->jiffies -= p->next->jiffies;
			// 交换除next以外的所有成员变量
			// 交换fn
			fn = p->fn;
			p->fn = p->next->fn;
			p->next->fn = fn;
			// 交换jiffies
			jiffies = p->jiffies;
			p->jiffies = p->next->jiffies;
			p->next->jiffies = jiffies;
			// 注意，这里只是交换jiffies、fn，不交换next。
			// 在while语句中实际上是遍历定时器链表，这个是while的下一次循环的准备工作
			p = p->next;  // 将p仍指向新增节点
		}
		// [[?？这段程序好象没有考虑周全。即如果新增的的定时器的jiffies值如果小于后面的定时器的jiffies值，那么while语句是不会被执行的，而强行加在了链表的开始处，而后面的定时器的jiffies并做相应的减操作，即后面所有的定时器都会延后到期。]]
		// 修改如下：
		// if (p->next && p->next->jiffies < p->jiffies)
		// 	while (p->next && p->next->jiffies < p->jiffies) {
		// 		// 父节点如果大于子节点，此时设置父节点的jiffies为二者的差，然后交换二者的链表位置，使父节点jiffies小于子节点
		// 		p->jiffies -= p->next->jiffies;
		// 		// 交换fn
		// 		fn = p->fn;
		// 		p->fn = p->next->fn;
		// 		p->next->fn = fn;
		// 		// 交换jiffies
		// 		jiffies = p->jiffies;
		// 		p->jiffies = p->next->jiffies;
		// 		p->next->jiffies = jiffies;
		// 		// 在while语句中实际上是遍历定时器链表，这个是while的准备工作
		// 		p = p->next;
		// 	}
		// else if (p->next && p->next->jiffies >= p->jiffies)
		// 	p->next->jiffies -= p->jiffies;

	}
	sti();
}

////时钟中断C函数处理程序，在 system_call.s 中的timer_interrupt(176 行）被调用。
//参数 cpl 是当前特权级0或3，是时钟中断发生时正被执行的代码选择符中的特权级。
//cpl=0 时表示中断发生时正在执行内核代码；cpl=3 时表示中断发生时正在执行用户代码。
//对于一个进程由于执行时间片用完时，则进行任务切换。并执行一个计时更新工作。
void do_timer(long cpl)
{
	extern int beepcount;
	extern void sysbeepstop(void);

	// 如果beepcount用完
	if (beepcount)
		if (!--beepcount)
			sysbeepstop();

	// 根据cpl取值，累计计算CPU的用户态和内核态的使用时间
	if (cpl)
		current->utime++;
	else
		current->stime++;

	if (next_timer) {
		next_timer->jiffies--;
		// 如果链表头节点的定时器已经到达
		while (next_timer && next_timer->jiffies <= 0) {
			void (*fn)(void);  // 定义一个函数指针变量fn
			
			fn = next_timer->fn;
			// 将链表头节点指向下一个节点
			next_timer->fn = NULL;
			next_timer = next_timer->next;
			// 执行处理函数
			(fn)();
		}
	}
	// 如果有软驱马达处于启动状态，则调用do_floppy_timer进行软驱的启动或关闭操作
	if (current_DOR & 0xf0)
		do_floppy_timer();
	// 如果当前进程还有时间片，则继续运行当前进程
	if ((--current->counter)>0) return;
	// 否则，进行重新调度（对于内核进程，不进行调度）
	current->counter=0;
	if (!cpl) return; // 对于内核态程序，不依赖 counter 值进行调度。
	schedule();
}

// 系统调用功能 - 设置报警定时时间值(秒）。
// 如果参数 seconds 大于 0，则设置新定时值，并返回原定时时刻还剩余的间隔时间。否则返回0。
// 报警定时到达，就会设置SIGALRM信号置位
int sys_alarm(long seconds)
{
	int old = current->alarm; // 进程数据结构中报警定时值 alarm 的单位是系统滴答，取值为’jiffies + HZ*定时秒值’，即=系统开机起到设置定时操作时系统滴答值jiffies + 转换成滴答单位的定时值，。

	if (old)
		old = (old - jiffies) / HZ;
	current->alarm = (seconds>0)?(jiffies+HZ*seconds):0;
	return (old);
}

int sys_getpid(void)
{
	return current->pid;
}

int sys_getppid(void)
{
	return current->father;
}

int sys_getuid(void)
{
	return current->uid;
}

int sys_geteuid(void)
{
	return current->euid;
}

int sys_getgid(void)
{
	return current->gid;
}

int sys_getegid(void)
{
	return current->egid;
}

// 设置优雅值
// nice值的范围-20(最高优先级)到19(最低优先级),默认为10 
int sys_nice(long increment)
{
	if (current->priority-increment>0)
		current->priority -= increment;
	return 0;
}

// 内核调度程序的初始化子程序。
void sched_init(void)
{
	int i;
	struct desc_struct * p;

	if (sizeof(struct sigaction) != 16)
		panic("Struct sigaction MUST be 16 bytes");
	// 设置gdt中tss描述符表项
	// gdt是描述符数组，gdt+4 是指针运算，结果为： &(gdt[4])
	set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
	set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
	// 从1号task开始，将其他所有task进行初始化：
	p = gdt+2+FIRST_TSS_ENTRY;
	for(i=1;i<NR_TASKS;i++) {
		task[i] = NULL;
		p->a=p->b=0;  // tss
		p++;
		p->a=p->b=0;  // ldt
		p++;
	}
/* Clear NT, so that we won't have troubles with that later on */
// NT 标志用于控制程序的递归调用(Nested Task)。
// 当NT 置位时，那么当前中断任务执行 iret 指令时就会引起任务切换。NT 指出TSS中的back_link 字段是否有效。
	// 将eflags中的NT为复位
	__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
	// 加载进程0的TSS表项到TR寄存器
	ltr(0); 	
	// 加载进程0的LDT表项到LDTR寄存器
	lldt(0);
	// 下面3行代码用于初始化8253 定时器。每10ms触发一次时间中断
	outb_p(0x36,0x43);		/* binary, mode 3, LSB/MSB, ch 0 */
	outb_p(LATCH & 0xff , 0x40);	/* LSB */
	outb(LATCH >> 8 , 0x40);	/* MSB */
	// 设置时间中断门（生成中断描述符表项）
	set_intr_gate(0x20,&timer_interrupt);
	// 修改中断控制器屏蔽码，允许时钟中断。
	outb(inb_p(0x21)&~0x01,0x21);
	// 设置系统调用中断门（生成中断描述符表项）
	set_system_gate(0x80,&system_call);
}
